/**
 *    Copyright 2009-2023 the original author or authors.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */
package org.apache.ibatis.mapping;

import org.apache.ibatis.transaction.TransactionFactory;

import javax.sql.DataSource;

/**
 * @author Clinton Begin
 */
public final class Environment {
  private final String id;
  /**
   * 事务管理器（transactionManager）。
   * <p>
   *   在 MyBatis 中有两种类型的事务管理器（也就是 type=”[JDBC|MANAGED]”）：
   *   <ul>
   *     <li>JDBC – 这个配置就是直接使用了 JDBC 的提交和回滚设置，它依赖于从数据源得到的连接来管理事务作用域。
   *     默认情况下，为了与某些驱动程序兼容，它在关闭连接时启用自动提交。然而，对于某些驱动程序来说，启用自动提交不仅是不必要的，而且是一个代价高昂的操作。
   *     因此，从 3.5.10 版本开始，你可以通过将 "skipSetAutoCommitOnClose" 属性设置为 "true" 来跳过这个步骤。例如：
   *     <pre>
   *       {@literal
   *       <transactionManager type="JDBC">
   *         <property name="skipSetAutoCommitOnClose" value="true"/>
   *       </transactionManager>
   *       }
   *       </pre>
   *     </li>
   *     <li>MANAGED – 这个配置几乎没做什么。它从来不提交或回滚一个连接，而是让容器来管理事务的整个生命周期（比如 JavaEE 应用服务器的上下文）。
   *     默认情况下它会关闭连接，然而一些容器并不希望这样，因此需要将 closeConnection 属性设置为 false 来阻止它默认的关闭行为。例如:
   *     <pre>
   *       {@literal
   *       <transactionManager type="MANAGED">
   *         <property name="closeConnection" value="false"/>
   *       </transactionManager>
   *       }
   *       </pre>
   *       </li>
   *   </ul>
   * </p>
   * _提示：如果你正在使用 Spring + MyBatis，则没有必要配置事务管理器， 因为 Spring 模块会使用自带的管理器来覆盖前面的配置。
   */
  private final TransactionFactory transactionFactory;
  /**
   *  数据源（dataSource）
   *  <p>
   *    dataSource 元素使用标准的 JDBC 数据源接口来配置 JDBC 连接对象的资源。
   *    <li>大多数 MyBatis 应用程序会按示例中的例子来配置数据源。虽然数据源配置是可选的，但如果要启用延迟加载特性，就必须配置数据源。</li>
   *    有三种内建的数据源类型（也就是 type="[UNPOOLED|POOLED|JNDI]"）：
   *    <ul>
   *      <li>UNPOOLED– 这个数据源的实现只是每次被请求时打开和关闭连接。虽然有点慢，但对于在数据库连接可用性方面没有太高要求的简单应用程序来说，是一个很好的选择。不同的数据库在性能方面的表现也是不一样的，对于某些数据库来说，使用连接池并不重要，这个配置就很适合这种情形。UNPOOLED 类型的数据源仅仅需要配置以下 5 种属性：
   *          <ul>
   *            <li>driver – 这是 JDBC 驱动的 Java 类全限定名（并不是 JDBC 驱动中可能包含的数据源类）。</li>
   *            <li>url – 这是数据库的 JDBC URL 地址。</li>
   *            <li>username – 登录数据库的用户名。</li>
   *            <li>password – 登录数据库的密码。</li>
   *            <li>defaultTransactionIsolationLevel – 默认的连接事务隔离级别。 </li>
   *            <li>defaultNetworkTimeout – 等待数据库操作完成的默认网络超时时间（单位：毫秒）。查看 java.sql.Connection#setNetworkTimeout() 的 API 文档以获取更多信息。 </li>
   *            作为可选项，你也可以传递属性给数据库驱动。只需在属性名加上“driver.”前缀即可，例如：
   *            <li>driver.encoding=UTF8</li>
   *            这将通过 DriverManager.getConnection(url, driverProperties) 方法传递值为 UTF8 的 encoding 属性给数据库驱动。
   *          </ul>
   *      </li>
   *      <li>POOLED– 这种数据源的实现利用“池”的概念将 JDBC 连接对象组织起来，避免了创建新的连接实例时所必需的初始化和认证时间。
   *      这是一种使得并发 Web 应用快速响应请求的流行处理方式。
   *        <ul>
   *          除了上述提到 UNPOOLED 下的属性外，还有更多属性用来配置 POOLED 的数据源：
   *          <li>poolMaximumActiveConnections – 在任意时间可存在的活动（正在使用）连接数量，默认值：10 </li>
   *          <li>poolMaximumIdleConnections – 任意时间可能存在的空闲连接数。 </li>
   *          <li>poolMaximumCheckoutTime – 在被强制返回之前，池中连接被检出（checked out）时间，默认值：20000 毫秒（即 20 秒） </li>
   *          <li>poolTimeToWait – 这是一个底层设置，如果获取连接花费了相当长的时间，连接池会打印状态日志并重新尝试获取一个连接（避免在误配置的情况下一直失败且不打印日志），默认值：20000 毫秒（即 20 秒）。 </li>
   *          <li>poolMaximumLocalBadConnectionTolerance – 这是一个关于坏连接容忍度的底层设置， 作用于每一个尝试从缓存池获取连接的线程。 如果这个线程获取到的是一个坏的连接，那么这个数据源允许这个线程尝试重新获取一个新的连接，但是这个重新尝试的次数不应该超过 poolMaximumIdleConnections 与 poolMaximumLocalBadConnectionTolerance 之和。 默认值：3（新增于 3.4.5） </li>
   *          <li>poolPingQuery – 发送到数据库的侦测查询，用来检验连接是否正常工作并准备接受请求。默认是“NO PING QUERY SET”，这会导致多数数据库驱动出错时返回恰当的错误消息。 </li>
   *          <li>poolPingEnabled – 是否启用侦测查询。若开启，需要设置 poolPingQuery 属性为一个可执行的 SQL 语句（最好是一个速度非常快的 SQL 语句），默认值：false。 </li>
   *          <li>poolPingConnectionsNotUsedFor – 配置 poolPingQuery 的频率。可以被设置为和数据库连接超时时间一样，来避免不必要的侦测，默认值：0（即所有连接每一时刻都被侦测 — 当然仅当 poolPingEnabled 为 true 时适用）。 </li>
   *        </ul>
   *      </li>
   *      <li>JNDI – 这个数据源的实现是为了能在如 EJB 或应用服务器这类容器中使用，容器可以集中或在外部配置数据源，然后放置一个 JNDI 上下文的引用。这种数据源配置只需要两个属性：
   *        <ul>
   *          <li>initial_context – 这个属性用来在 InitialContext 中寻找上下文（即，initialContext.lookup(initial_context)）。这是个可选属性，如果忽略，那么将会直接从 InitialContext 中寻找 data_source 属性。 </li>
   *          <li>data_source – 这是引用数据源实例位置的上下文路径。提供了 initial_context 配置时会在其返回的上下文中进行查找，没有提供时则直接在 InitialContext 中查找。 </li>
   *          和其他数据源配置类似，可以通过添加前缀“env.”直接把属性传递给 InitialContext。比如：
   *          <li>env.encoding=UTF8</li>
   *     这就会在 InitialContext 实例化时往它的构造方法传递值为 UTF8 的 encoding 属性。
   *        </ul>
   *      </li>
   *    </ul>
   *  </p>
   */
  private final DataSource dataSource;

  /**
   * Environment - 构造函数
   *
   * @param id                 environment id
   * @param transactionFactory 事务工厂
   * @param dataSource         数据源
   */
  public Environment(String id, TransactionFactory transactionFactory, DataSource dataSource) {
    if (id == null) {
      throw new IllegalArgumentException("Parameter 'id' must not be null");
    }
    if (transactionFactory == null) {
      throw new IllegalArgumentException("Parameter 'transactionFactory' must not be null");
    }
    this.id = id;
    if (dataSource == null) {
      throw new IllegalArgumentException("Parameter 'dataSource' must not be null");
    }
    this.transactionFactory = transactionFactory;
    this.dataSource = dataSource;
  }

  public static class Builder {
    private final String id;
    private TransactionFactory transactionFactory;
    private DataSource dataSource;

    public Builder(String id) {
      this.id = id;
    }

    public Builder transactionFactory(TransactionFactory transactionFactory) {
      this.transactionFactory = transactionFactory;
      return this;
    }

    public Builder dataSource(DataSource dataSource) {
      this.dataSource = dataSource;
      return this;
    }

    public String id() {
      return this.id;
    }

    public Environment build() {
      return new Environment(this.id, this.transactionFactory, this.dataSource);
    }

  }

  public String getId() {
    return this.id;
  }

  public TransactionFactory getTransactionFactory() {
    return this.transactionFactory;
  }

  public DataSource getDataSource() {
    return this.dataSource;
  }

}
